package student.lock;

import cn.hutool.core.lang.Console;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 利用读写锁实现缓存并发
 * 并发读，阻塞写
 *
 * /**
 *  * 场景：
 *  *     现实中有这样一种场景：对共享资源有读和写的操作，且写操作没有读操作那么频繁；
 *  *     在没有写操作的时候，多个线程同时读一个资源没有任何问题，所以应该允许多个线程同时读取共享资源；
 *  *     但是如果一个线程想去写这些共享资源，就不应该允许其他线程对该资源进行读和写的操作了；
 *  * 读写锁：
 *  *    JAVA的并发包提供了读写锁ReentrantReadWriteLock，它表示两个锁，一个是读操作相关的锁，称为共享锁；一个是写相关的锁，称为排他锁；
 *  *    一个ReadWriteLock维护一对关联的锁，一个用于只读操作一个用于写。在没有写线程的情况下一个读锁可能会同时被多个读线程持有；
 *  *    写锁是独占的，你可以使用JDK中的ReentrantReadWriteLock来实现这个规则，它最多支持65535个写锁和65535个读锁；
 *  *
 */
public class ReadWriteMapCache {

    /**
     * 缓存数据存放Map
     */
    private final Map<String,Object> cashMap = new HashMap<>();

    /**
     * 使用读写锁
     */
    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    /**
     * 插入数据
     * @param key
     * @param value
     */
    public void put(String key,Object value) {
        //加入写锁
        lock.writeLock().lock();
        try {
            Console.log("开始插入数据：" + key + Thread.currentThread().getName());
            cashMap.put(key,value);
            Console.log("插入数据成功："+ key+Thread.currentThread().getName());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.writeLock().unlock();
        }
    }

    /**
     * 读取数据
     * @param key
     */
    public  void get(String key) {
        //加入读锁
        lock.readLock().lock();
        try {
            Console.log("开始读取数据："+ key + Thread.currentThread().getName());
            Object result = cashMap.get(key);
            Console.log("读取数据成功："+ result + Thread.currentThread().getName());
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.readLock().unlock();
        }
    }

    static class ReadWriteDemo {
        public static void main(String[] args) {
            ReadWriteMapCache cache = new ReadWriteMapCache();
            for (int i = 0; i < 5; i++) {
                final  int num = i;
                new Thread(() -> {
                    try {
                        cache.put(num+"",num+"");
                        TimeUnit.MILLISECONDS.sleep(300);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                },"插入线程："+i).start();
            }

            for (int i = 0; i < 5; i++) {
                final  int num = i;
                new Thread(() -> {
                    try {
                        cache.get(num+"");
                        TimeUnit.MILLISECONDS.sleep(300);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                },"读取线程："+i).start();
            }
        }
    }
}
